日常生活中的数学:保险公司推销的理财产品

数学,在日常生活中出了买菜之外,还能发挥很大的作用。笔者的一个朋友在朋友圈发布了一条保险的广告:

简单的说:连续三年每年存3万,然后第八年就能够取出来 111,659元。根据他们的说法,第八年的收益是 21,659元,因此利息是:(21659/90000)=24.1%,这算得每年利息 24.1%/7=3.45% (我也不知道为什么他们要除以7 )。从直觉上看我感觉这个非常可疑,按照复利计算得年利率应该不高。于是动笔进行计算。

假设年利率为x,那么一年之后本金加利率为  p=1+x。

第一年末尾一共有: 3*p

第二年末尾一共有: (3*p+3)*p

第三年末尾一共有: ((3*p+3)*p+3)*p

第四年末尾一共有: ((3*p+3)*p+3)*(p^2)

……………………………………….

第八年末尾一共有: ((3*p+3)*p+3)*(p^6)

第N年末尾一共有: ((3*p+3)*p+3)*(p^(N-2))

针对第八年进行研究,展开算式一共有 3*(P^8) +3*(P^7) +3*(P^6), 对照上面表格有方程

3*(P^8) +3*(P^7) +3*(P^6)= 111659

这是一元八次方程,我没有办法直接从数学角度解开。于是编写代码来解。因为我们知道这是一个单调递增函数,所以我们可以给定一个初始值Start,然后给出一个步长 Step,不断尝试计算f(Start+Step)的值,如果它小于目标,那么 Start=Start+Step,否则 Step=Step/2 继续尝试。最终得到一个值: 1.03123863220215

换句话说,以复利计算年利率 3.123%。

之后,我们再使用20年的收益168703进行计算,结果是:1.03360308647156。以复利计算年利率 3.36%。

C#编写的完整代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ConsoleApplication31
{
    class Program
    {
        static double f(double value,int year)
        {
            return Math.Pow(value, year) + Math.Pow(value, year-1) + Math.Pow(value, year-2);
        }
        static void Main(string[] args)
        {

            const double ESP = 1e-6;
            const int YEAR = 8;
            double start = 1.00;
            double step = 0.01;
            double target = 111659.00 / 30000;
            for (int i = 0; i < 1000; i++)
            {
                if (Math.Abs(f(start, YEAR) - target) <ESP)
                {
                    Console.WriteLine("Target: {0}", start);
                    break;
                }
                // 增加之后 f(x) 大于 target
                if (f(start + step, YEAR) > target)
                {
                    step = step / 2;
                }
                else {
                    start += step;
                }
                Console.WriteLine("{0}:{1} {2}", i, f(start, YEAR), start);
            }
            Console.ReadKey();
        }
    }
}

从上面可以看出:在查看这种收益表格时,因为计算复杂,消费者很容易被误导。另外,这种产品周期太长,风险很大,是另外的成本。

Step to UEFI (251)Register PPI Notify

St这个系列是根据 QEMU 执行输出的 Log 来研究代码的。这里继续跟踪研究代码,这次研究的对象是下面这条串口输出:

Register PPI Notify: DCD0BE23-9586-40F4-B643-06522CED4EDE

这句话来自 \MdeModulePkg\Core\Pei\Security\Security.c 文件中的InitializeSecurityServices() 函数:

/**
  Initialize the security services.

  @param PeiServices     An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation.
  @param OldCoreData     Pointer to the old core data.
                         NULL if being run in non-permanent memory mode.

**/
VOID
InitializeSecurityServices (
  IN EFI_PEI_SERVICES  **PeiServices,
  IN PEI_CORE_INSTANCE *OldCoreData
  )
{
  if (OldCoreData == NULL) {
    PeiServicesNotifyPpi (&mNotifyList);
  }
  return;
}

其中 mNotifyList 注册了一个 Ppi Notify 的 Callback 函数:SecurityPpiNotifyCallback()

EFI_PEI_NOTIFY_DESCRIPTOR mNotifyList = {
   EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST,
   &gEfiPeiSecurity2PpiGuid,
   SecurityPpiNotifyCallback
};

PeiServicesNotifyPpi() 的定义在 \MdePkg\Library\PeiServicesLib\PeiServicesLib.c:

/**
  This service enables PEIMs to register a given service to be invoked when another service is
  installed or reinstalled.

  @param  NotifyList            A pointer to the list of notification interfaces
                                that the caller shall install.

  @retval EFI_SUCCESS           The interface was successfully installed.
  @retval EFI_INVALID_PARAMETER The NotifyList pointer is NULL.
  @retval EFI_INVALID_PARAMETER Any of the PEI notify descriptors in the list do
                                 not have the EFI_PEI_PPI_DESCRIPTOR_NOTIFY_TYPES
                                 bit set in the Flags field.
  @retval EFI_OUT_OF_RESOURCES  There is no additional space in the PPI database.

**/
EFI_STATUS
EFIAPI
PeiServicesNotifyPpi (
  IN CONST EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyList
  )

这个函数的功能是:注册一个 Callback函数,当给定的 Service 安装(install)或者重安装(re-install)时触发这个 callback函数。

经过检查,代码中没有人安装gEfiPeiSecurity2PpiGuid,所以 CallBack 不会发生。接下来找一个有触发 CallBack 函数的作为例子看一下这个如何动作的。

串口输出” Register PPI Notify: 49EDB1C1-BF21-4761-BB12-EB0031AABB397” 和上面的函数类似,这个 GUID定义在  \MdePkg\MdePkg.dec 中:

## Include/Ppi/FirmwareVolumeInfo.h
  gEfiPeiFirmwareVolumeInfoPpiGuid = { 0x49edb1c1, 0xbf21, 0x4761, { 0xbb, 0x12, 0xeb, 0x0, 0x31, 0xaa, 0xbb, 0x39 } }

1. 首先注册Notify,在\MdeModulePkg\Core\Pei\FwVol\FwVol.c 中:

EFI_PEI_NOTIFY_DESCRIPTOR mNotifyOnFvInfoList[] = {
  {
    EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK,
    &gEfiPeiFirmwareVolumeInfoPpiGuid,
    FirmwareVolumeInfoPpiNotifyCallback
  },
  {
    (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
    &gEfiPeiFirmwareVolumeInfo2PpiGuid,
    FirmwareVolumeInfoPpiNotifyCallback
  }
};

接下来可以在在串口 Log 中找到如下字样:

Install PPI: 49EDB1C1-BF21-4761-BB12-EB0031AABB39
Notify: PPI Guid: 49EDB1C1-BF21-4761-BB12-EB0031AABB39, Peim notify entry point: 82153C

2. “Install PPI: 49EDB1C1-BF21-4761-BB12-EB0031AABB39”来自:

\MdePkg\Library\PeiServicesLib\PeiServicesLib.c 中的InternalPeiServicesInstallFvInfoPpi () 函数,首先给PpiGuid赋值

    //
    // To install FvInfo Ppi.
    //
    FvInfoPpi = AllocateZeroPool (sizeof (EFI_PEI_FIRMWARE_VOLUME_INFO_PPI));
    ASSERT (FvInfoPpi != NULL);
PpiGuid = &gEfiPeiFirmwareVolumeInfoPpiGuid;

接下来PeiServicesInstallPpi (FvInfoPpiDescriptor) 会安装这个 Ppi:

  FvInfoPpiDescriptor->Guid  = PpiGuid;
  FvInfoPpiDescriptor->Flags = EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST;
  FvInfoPpiDescriptor->Ppi   = (VOID *) FvInfoPpi;
  Status = PeiServicesInstallPpi (FvInfoPpiDescriptor);

安装过程会出现“Install PPI: 49EDB1C1-BF21-4761-BB12-EB0031AABB39” 的提示。

3. “Notify: PPI Guid: 49EDB1C1-BF21-4761-BB12-EB0031AABB39, Peim notify entry point: 82153C”

因为这里安装了gEfiPeiFirmwareVolumeInfoPpiGuid 所以会执行FirmwareVolumeInfoPpiNotifyCallback() 这个 Callback 函数。在函数中有定义

    DEBUG ((
      EFI_D_INFO,
      "The %dth FV start address is 0x%11p, size is 0x%08x, handle is 0x%p\n",
      (UINT32) CurFvCount,
      (VOID *) FvInfo2Ppi.FvInfo,
      FvInfo2Ppi.FvInfoSize,
      FvHandle
      ));

于是我们在串口上能看到如下 Log :

“The 1th FV start address is 0x00000900000, size is 0x00C00000, handle is 0x900000”

就是说按照我们的预期一样,PeiServicesNotifyPpi 函数是用来注册一个 Callback 函数,当安装给定 GUID  的Service 后,触发这个 Callback 函数。